/***********************************************************************************************************************
 * 
 * > QuadroCore <
 * 
 * Copyright (C) 2012 by Chris Channing
 *
 ***********************************************************************************************************************
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 *
 ***********************************************************************************************************************/

#include "quadrocore.h"

void LCDSetSelected(bool_t selected);
void LCDSetCommandMode(void);
void LCDSetDataMode(void);
void LCDTransmit(uint8_t value);
void LCDInitSPI(void);

void LCDInitIO(void)
{
	// initialize the transmitter and I/O pins
	LCDInitSPI();
	
	// make sure the hardware is reset from scratch
	LCDResetIO();
	
	// The column address is set to increasing
	LCDWriteCommand(LCD_CMD_ADC_NORMAL);

	// Non-inverted display
	LCDWriteCommand(LCD_CMD_DISPLAY_NORMAL);

	// The common mode scan direction is reversed COM31->COM0
	LCDWriteCommand(LCD_CMD_REVERSE_SCAN_DIRECTION);

	// Set the voltage bias ratio to 1/6
	LCDWriteCommand(LCD_CMD_LCD_BIAS_1_DIV_6_DUTY33);

	// Set booster circuit, voltage regulator and voltage follower all to on
	LCDWriteCommand(LCD_CMD_POWER_CTRL_ALL_ON);
	
	// allow the power to stabilize
	wait(2);
	
	// Set the booster ratio to 2X,3X,4X
	LCDWriteCommand(LCD_CMD_BOOSTER_RATIO_SET);
	LCDWriteCommand(LCD_CMD_BOOSTER_RATIO_2X_3X_4X);

	// Set voltage resistor ratio to 1
	LCDWriteCommand(LCD_CMD_VOLTAGE_RESISTOR_RATIO_1);

	// Set contrast
	LCDWriteCommand(LCD_CMD_ELECTRONIC_VOLUME_MODE_SET);
	LCDWriteCommand(LCD_CMD_ELECTRONIC_VOLUME(LCD_DISPLAY_CONTRAST_MIN));
	
	// switch on the LCD circuit ready for data
	LCDWriteCommand(LCD_CMD_DISPLAY_ON);
}

void LCDInitSPI(void)
{
	// prepare the chip select (CS) pin
	PortSetPinHigh(&PORTF, 3);
	
	// prepare the register select pin (A0)
	PortSetPinHigh(&PORTD, 0);
	
	// prepare the reset pin (RST)
	PortSetPinHigh(&PORTA, 3);
	
	// prepare the MOSI pin (MOSI)
	PortSetPinHigh(&PORTD, 3);
	
	// prepare the clock pin (CLK/SCL)
	PortSetPinLow(&PORTD, 1);
	
	// invert the clock pin for PORTD (part of mode 3)
	PORTD.PIN1CTRL = PORT_INVEN_bm;
	
	// configure the internal clock generator to the correct baud rate
	{
		uint16_t bsel = (CPU_HZ / (2 * LCD_BAUD)) - 1;
		
		// keep the lower 8 bits
		USARTD0.BAUDCTRLA = (bsel & 0x00FF);
		// keep the upper 4 bits (we don't need bscale)
		USARTD0.BAUDCTRLB = ((bsel >> 8) & 0x0F);
	}
	
	// enable MSPI mode (UDORD to MSB first, UCPHA = 1 (part of mode 3))
	USARTD0.CTRLC = (USART_CMODE_MSPI_gc | 0x02);
	
	// enable transmitter
	USARTD0.CTRLB = USART_TXEN_bm;
}

void LCDResetIO(void)
{
	LCDSetSelected(true);
	{
		PortSetPinLow(&PORTA, 3);
		wait(5);
		PortSetPinHigh(&PORTA, 3);
		wait(5);
	}
	LCDSetSelected(false);		
}

void LCDWriteCommand(uint8_t command)
{
	LCDSetSelected(true);
	{
		LCDSetCommandMode();
		LCDTransmit(command);
		
	}
	LCDSetSelected(false);
}

void LCDWriteLongCommand(uint8_t upperCommand, uint8_t lowerCommand)
{
	LCDSetSelected(true);
	{
		LCDSetCommandMode();
		LCDTransmit(upperCommand);
		LCDTransmit(lowerCommand);
	}
	LCDSetSelected(false);
}

void LCDWriteData(uint8_t data)
{		
	LCDSetSelected(true);
	{
		LCDSetDataMode();
		LCDTransmit(data);
	}
	LCDSetSelected(false);
}

void LCDSetSelected(bool_t selected)
{
	if (selected)
	{
		PortSetPinLow(&PORTF, 3);
	}
	else
	{
		PortSetPinHigh(&PORTF, 3);
	}
}

void LCDSetCommandMode(void)
{
	PortSetPinLow(&PORTD, 0);
}

void LCDSetDataMode(void)
{
	PortSetPinHigh(&PORTD, 0);
}

void LCDTransmit(uint8_t value)
{
	while (USART_DREIF_bm != (USARTD0.STATUS & USART_DREIF_bm))
	{
		// spin until the data register is ready
	}
	
	// transmit data
	USARTD0.DATA = value;
	
	while (USART_TXCIF_bm != (USARTD0.STATUS & USART_TXCIF_bm))
	{
		// spin until the data has been sent through to the LCD
	}
	
	// clear the tx complete flag
	USARTD0.STATUS = USART_TXCIF_bm;
}

void LCDClear(void)
{
	for (int page = 0; page < LCD_PAGES; page++)
	{
		LCDWriteCommand(LCD_CMD_PAGE_ADDRESS_SET(page));
		LCDWriteLongCommand(LCD_CMD_COLUMN_ADDRESS_SET_MSB(0), LCD_CMD_COLUMN_ADDRESS_SET_LSB(0));
					
		// the LCD will automatically increment the column number after each write
		for (int column = 0; column < LCD_WIDTH; column++)
		{
			LCDWriteData(0x00);
		}
	}
}